/*
 * @(#)AbstractCacheDao.java 2011-11-09
 *
 * Copyright 2011 北龙中网（北京）科技有限责任公司. All rights reserved.
 */
package cn.shangjia.shop.dao;

import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.annotation.Resource;

import cn.shangjia.common.cache.memcache.CacheClient;
import cn.shangjia.common.dao.SuperDao;
import cn.shangjia.common.dao.support.Page;
import cn.shangjia.common.domain.Domain;
import cn.shangjia.common.exceptions.Logger;
import cn.shangjia.common.util.EncodeUtils;
import cn.shangjia.common.util.StringUtils;
import cn.shangjia.shop.log.ShopLoggerFactory;

/**
 * 缓存的实现.
 *
 * @author <a href="mailto:jinzhongze@knet.cn">jinzhongze</a>
 * @version 1.0, 2011-11-09 10:51:07
 * @since 1.0
 */
public abstract class AbstractCacheDao<T extends Domain> extends SuperDao<T>{

	protected final Logger logger = ShopLoggerFactory.getLogger(getClass());
	
	@Resource
	private CacheClient cacheClient ;
	
	/**
	 *  该缓存基类  所支持的 缓存类型
	 * @author jinzhongze
	 *
	 */
	public enum CacheType {
		OBJ,           //object 类型
		LIST,          //list 类型
		CNT,	         //count 类型 
		KEY          // 缓存 key 类型 - Set<String>
	}
	
	/**
	 *  该方法用来 标识 该表的 列表散列主键 . 默认是 userId
	 *  若 该表的 散列主键 不是 userId . 例如 企业动态的评论列表 . 
	 *  那么 应该重写 此方法 来标识 散列主键
	 * 
	 * @return
	 * @author <a href="mailto:jinzhongze@knet.cn">jinzhongze</a>
	 */
	protected String getPK() {
		return "userId";
	}
	
	/**
	 *  以查询条件获得key
	 *
	 * @param cacheType
	 * @param params
	 * @return
	 * @author <a href="mailto:jinzhongze@knet.cn">jinzhongze</a>
	 */
	public String getKey(CacheType cacheType , Map<String, Object> params) {
		
		if (cacheType == null || params == null || params.isEmpty()) 
			return null;
		
		String pk = (String) params.get(getPK());
		
		if (StringUtils.isEmpty(pk)) {
			logger.warn("[dao层]-[cache]-获取key有异常发生, 列表key散列主键 为空 !!! , 散列标识 为 : " , getPK() );
			return null;
		}
		
		//拼装条件 并且转义成MD5定长值
		String tmp = "";
		for (String key : params.keySet()) {
			tmp += (key  + params.get(key));
		}
		String paramsMd5 = EncodeUtils.md5Hex(tmp);
		//拼出符合类型的 MD5定长型的key. 
		String key = getKey(cacheType, pk, paramsMd5);
		return key;
	}
	
	/**
	 *  以主键约束获得key
	 * 
	 * @param cacheType
	 * @param id
	 * @return
	 * @author <a href="mailto:jinzhongze@knet.cn">jinzhongze</a>
	 */
	public String getKey(CacheType cacheType, String id) {
		if (cacheType == null || StringUtils.isEmpty(id)) 
			return null;
		
		return getKey(cacheType, id, null);
	}
	
	private String getKey(CacheType cacheType, String pk , String paramsMd5) {
		try {
			StringBuffer sb = new StringBuffer();
			sb.append(getClassName()); //拼上要操作的对象名称
			sb.append("-").append(cacheType); //拼上缓存类型
			if (!StringUtils.isEmpty(paramsMd5)) //如果有条件类型的话拼上条件类型定长值
				sb.append("-").append(paramsMd5);
			sb.append("-").append(pk); //用于标识当前key,  type + id 的复合主键
			
			logger.debug("cache - key = " , sb.toString());
			
			return sb.toString();
		} catch (Exception e)  {
			logger.warn("[dao层]-[cache]-获取key有异常发生,type=",cacheType," , pk = " , pk, ", paramsMd5 = " , paramsMd5);
			return null;
		}
	}
	
	/**
	 *  从缓存中拿取一个对象
	 * 
	 * @param key
	 * @return
	 * @author <a href="mailto:jinzhongze@knet.cn">jinzhongze</a>
	 */
	public <X> X get(String key) {
		if (key == null) 
			return null;
		try {
			return cacheClient.get(key);
		} catch (Exception e) {
			logger.error(e, "获取缓存value异常!",key);
			return null;
		}
	}
	
	
	/**
	 *  在缓存中添加一个对象
	 * 
	 * @param key
	 * @param src
	 * @author <a href="mailto:jinzhongze@knet.cn">jinzhongze</a>
	 */
	public void add(String key , Object src) {
		if (key == null) {
			logger.warn("存入缓存key为null, value = " , src);
			return ;
		}
		try {
			cacheClient.set(key , 0, src);
		} catch (Exception e) {
			logger.error(e, "存入缓存value异常!",key,src);
		}
	}
	
	/**
	 *  替换缓存中一个对象
	 * 
	 * @param key
	 * @param src
	 * @author <a href="mailto:jinzhongze@knet.cn">jinzhongze</a>
	 */
	public void replace(String key , Object src) {
		if (key == null) {
			logger.warn("更新缓存key为null, value = " , src);
			return ;
		}
		try {
			cacheClient.replace(key, 0, src);
		} catch (Exception e) {
			logger.error(e, "更新缓存value异常!",key,src);
		}
	}
	
	/**
	 *  从缓存中删除一个对象
	 * 
	 * @param key
	 * @return
	 * @author <a href="mailto:jinzhongze@knet.cn">jinzhongze</a>
	 */
	public void remove(String key) {
		if (key == null) {
			logger.warn("删除缓存key为null");
			return ;
		}
		try {
			cacheClient.delete(key);
		} catch (Exception e) {
			logger.error(e, "删除缓存value异常!",key);
		}
	}
	
	/**
	 * 以散列主键 产生 key, 将 count-key 以及 list-key 放入 Set<String> 进行缓存 
	 * @param userId
	 * @param key
	 * @author <a href="mailto:jinzhongze@knet.cn">jinzhongze</a>
	 */
	public void addKeys(String pk, String srcKey) {
		
		if (StringUtils.isEmpty(pk) || StringUtils.isEmpty(srcKey))
			return;
		
		String key = getKey(CacheType.KEY, pk);
		Set<String> set = get(key);
		if (set == null) {
			set = new HashSet<String>();
			add(key, set);
		}
		set.add(srcKey);
		replace(key, set);
	}
	
	/**
	 * 以散列主键 删除 缓存keys中的key
	 * 
	 * @param pk
	 * @param srcKey
	 * @author <a href="mailto:jinzhongze@knet.cn">jinzhongze</a>
	 */
	public void delKeys(String pk, String srcKey) {
		
		if (StringUtils.isEmpty(pk) || StringUtils.isEmpty(srcKey))
			return;
		String key = getKey(CacheType.KEY, pk);
		Set<String> set = get(key);
		if (set == null || set.isEmpty())
			return;
		set.remove(srcKey);
		replace(key, set);
	}
	
	/**
	 *  通过散列主键 获取缓存keys
	 * 
	 * @param pk
	 * @return
	 * @author <a href="mailto:jinzhongze@knet.cn">jinzhongze</a>
	 */
	public Set<String> getKeys(String pk) {
		
		String key = getKey(CacheType.KEY, pk);
		Set<String> set = get(key);
		if (set == null) 
			return new HashSet<String>();
		return set;
		
	}
	
	//----------------------   重写 一些常用的方法---------------------------------
	
	/*
	 * (non-Javadoc)
	 * @see cn.shangjia.common.dao.SuperDao#read(java.lang.String)
	 */
	@Override
	public T read(String id) {
		
		String key = getKey(CacheType.OBJ, id);
		
		T t = get(key);
		if (t != null)
			return t;
		//如果没有缓存
		t = super.read(id);
		if (t == null) 
			return null;
		add(key, t); //存入缓存
		return t;
	}
	
	/* (non-Javadoc)
	 * @see cn.shangjia.common.dao.SuperDao#selectOne(java.util.Map)
	 */
	@Override
	public T selectOne(Map<String, Object> parames) {
		
		String pk = (String) parames.get(getPK());
		if (StringUtils.isEmpty(pk))
			return super.selectOne(parames);
		
		//由于 商铺 没有复合的单记录查询. 所以直接重写了selectOne方法. 
		//如果 有复合条件的查询 可以重写或者重定义 自定义主键获取数据的方法
		String key = getKey(CacheType.OBJ, pk);
		
		T t = get(key);
		if (t != null)
			return t;
		//如果没有缓存
		t = super.selectOne(parames);
		if (t == null) 
			return null;
		add(key, t); //存入缓存
		return t;
	}
	
	/*
	 * (non-Javadoc)
	 * @see cn.shangjia.common.dao.SuperDao#totalCount(java.util.Map)
	 */
	@Override
	public long totalCount(Map<String, Object> parames) {
		
		String key = getKey(CacheType.CNT, parames);
		
		Long t = get(key);
		
		if (t != null)
			return t.longValue();
		
		//如果没有缓存
		t = super.totalCount(parames);
		
		add(key, t); //存入缓存
		
		String pk = (String) parames.get(getPK());
		addKeys(pk, key); //把key缓存
		
		return t;
	}
	
	/*
	 * (non-Javadoc)
	 * @see cn.shangjia.common.dao.SuperDao#selectListPage(cn.shangjia.common.dao.support.Page, java.util.Map)
	 */
	@Override
	public Page<T> selectListPage(Page<T> page, Map<String, Object> parames) {
		
		Map<String, Object> params = getPageParames(page, parames);  //带分页参数
		String key = getKey(CacheType.LIST, params);
		
		Collection<T> t = get(key);
		if (t != null) {
			page.setResult(t);
			return page;
		}
		//如果没有缓存
		t = super.selectListPage(page, parames).getResult();
		page.setResult(t);
		//selectListPage方法 有判断 不会返回null
		add(key, t); //存入缓存
		
		String pk = (String) parames.get(getPK());
		addKeys(pk, key); //把key缓存
		
		return page;
	}
	
	//-------------------------------------- 如果 不需要 缓存 则 调用如下方法-----------------------------
	
	public T readNoCache(String id) {
		return super.read(id);
	}
	
	public T selectOneNoCache(Map<String, Object> parames) {
		return super.selectOne(parames);
	}
	
	public long totalCountNoCache(Map<String, Object> parames) {
		return super.totalCount(parames);
	}
	
	public Page<T> selectListPageNoCache(Page<T> page, Map<String, Object> parames) {
		return super.selectListPage(page, parames);
	}

}
